3 /***************************************************************************\
4 * SPIP, Systeme de publication pour l'internet *
6 * Copyright (c) 2001-2019 *
7 * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James *
9 * Ce programme est un logiciel libre distribue sous licence GNU/GPL. *
10 * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. *
11 \***************************************************************************/
13 if (!defined('_ECRIRE_INC_VERSION')) {
18 * Validateur XML en deux passes, fondé sur SAX pour la première
21 * Faudrait faire deux classes car pour la première passe
22 * on a les memes methodes et variables que l'indenteur
26 // http://code.spip.net/@validerElement
27 public function validerElement($phraseur, $name, $attrs) {
28 if (!($p = isset($this->dtc
->elements
[$name]))) {
29 if ($p = strpos($name, ':')) {
30 $name = substr($name, $p +
1);
31 $p = isset($this->dtc
->elements
[$name]);
34 coordonnees_erreur($this, " <b>$name</b> : "
35 . _T('zxml_inconnu_balise'));
40 // controler les filles illegitimes, ca suffit
41 $depth = $this->depth
;
42 $ouvrant = $this->ouvrant
;
43 #spip_log("trouve $name apres " . $ouvrant[$depth]);
44 if (isset($ouvrant[$depth])) {
45 if (preg_match('/^\s*(\w+)/', $ouvrant[$depth], $r)) {
47 #spip_log("pere $pere");
48 if (isset($this->dtc
->elements
[$pere])) {
49 $fils = $this->dtc
->elements
[$pere];
50 #spip_log("rejeton $name fils " . @join(',',$fils));
51 if (!($p = @in_array
($name, $fils))) {
52 if ($p = strpos($name, ':')) {
53 $p = substr($name, $p +
1);
54 $p = @in_array
($p, $fils);
58 $bons_peres = @join
('</b>, <b>', $this->dtc
->peres
[$name]);
59 coordonnees_erreur($this, " <b>$name</b> "
65 : ('<p style="font-size: 80%"> ' . _T('zxml_mais_de') . ' <b>' . $bons_peres . '</b></p>')));
66 } elseif ($this->dtc
->regles
[$pere][0] == '/') {
67 $frat = substr($depth, 2);
68 if (!isset($this->fratrie
[$frat])) {
69 $this->fratrie
[$frat] = '';
71 $this->fratrie
[$frat] .= "$name ";
76 // Init de la suite des balises a memoriser si regle difficile
77 if ($this->dtc
->regles
[$name] and $this->dtc
->regles
[$name][0] == '/') {
78 $this->fratrie
[$depth] = '';
80 if (isset($this->dtc
->attributs
[$name])) {
81 foreach ($this->dtc
->attributs
[$name] as $n => $v) {
82 if (($v[1] == '#REQUIRED') and (!isset($attrs[$n]))) {
83 coordonnees_erreur($this, " <b>$n</b>"
85 . _T('zxml_obligatoire_attribut')
92 // http://code.spip.net/@validerAttribut
93 public function validerAttribut($phraseur, $name, $val, $bal) {
94 // Si la balise est inconnue, eviter d'insister
95 if (!isset($this->dtc
->attributs
[$bal])) {
99 $a = $this->dtc
->attributs
[$bal];
100 if (!isset($a[$name])) {
101 $bons = join(', ', array_keys($a));
103 $bons = " title=' " .
104 _T('zxml_connus_attributs') .
109 $bons .= " style='font-weight: bold'";
110 coordonnees_erreur($this, " <b>$name</b> "
111 . _T('zxml_inconnu_attribut') . ' ' . _T('zxml_de')
112 . " <a$bons>$bal</a> ("
113 . _T('zxml_survoler')
116 $type = $a[$name][0];
117 if (!preg_match('/^\w+$/', $type)) {
118 $this->valider_motif($phraseur, $name, $val, $bal, $type);
120 if (method_exists($this, $f = 'validerAttribut_' . $type)) {
121 $this->$f($phraseur, $name, $val, $bal);
124 # else spip_log("$type type d'attribut inconnu");
128 public function validerAttribut_NMTOKEN($phraseur, $name, $val, $bal) {
129 $this->valider_motif($phraseur, $name, $val, $bal, _REGEXP_NMTOKEN
);
132 public function validerAttribut_NMTOKENS($phraseur, $name, $val, $bal) {
133 $this->valider_motif($phraseur, $name, $val, $bal, _REGEXP_NMTOKENS
);
136 // http://code.spip.net/@validerAttribut_ID
137 public function validerAttribut_ID($phraseur, $name, $val, $bal) {
138 if (isset($this->ids
[$val])) {
139 list($l, $c) = $this->ids
[$val];
140 coordonnees_erreur($this, " <p><b>$val</b> "
141 . _T('zxml_valeur_attribut')
148 $this->valider_motif($phraseur, $name, $val, $bal, _REGEXP_ID
);
149 $this->ids
[$val] = array(xml_get_current_line_number($phraseur), xml_get_current_column_number($phraseur));
153 // http://code.spip.net/@validerAttribut_IDREF
154 public function validerAttribut_IDREF($phraseur, $name, $val, $bal) {
155 $this->idrefs
[] = array($val, xml_get_current_line_number($phraseur), xml_get_current_column_number($phraseur));
158 // http://code.spip.net/@validerAttribut_IDREFS
159 public function validerAttribut_IDREFS($phraseur, $name, $val, $bal) {
160 $this->idrefss
[] = array($val, xml_get_current_line_number($phraseur), xml_get_current_column_number($phraseur));
163 // http://code.spip.net/@valider_motif
164 public function valider_motif($phraseur, $name, $val, $bal, $motif) {
165 if (!preg_match($motif, $val)) {
166 coordonnees_erreur($this, "<b>$val</b> "
167 . _T('zxml_valeur_attribut')
171 . _T('zxml_non_conforme')
173 . "<b>" . $motif . "</b>");
177 // http://code.spip.net/@valider_idref
178 public function valider_idref($nom, $ligne, $col) {
179 if (!isset($this->ids
[$nom])) {
180 $this->err
[] = array(" <p><b>$nom</b> " . _T('zxml_inconnu_id'), $ligne, $col);
184 // http://code.spip.net/@valider_passe2
185 public function valider_passe2() {
187 foreach ($this->idrefs
as $idref) {
188 list($nom, $ligne, $col) = $idref;
189 $this->valider_idref($nom, $ligne, $col);
191 foreach ($this->idrefss
as $idref) {
192 list($noms, $ligne, $col) = $idref;
193 foreach (preg_split('/\s+/', $noms) as $nom) {
194 $this->valider_idref($nom, $ligne, $col);
200 // http://code.spip.net/@debutElement
201 public function debutElement($phraseur, $name, $attrs) {
202 if ($this->dtc
->elements
) {
203 $this->validerElement($phraseur, $name, $attrs);
206 if ($f = $this->process
['debut']) {
207 $f($this, $name, $attrs);
209 $depth = $this->depth
;
210 $this->debuts
[$depth] = strlen($this->res
);
211 foreach ($attrs as $k => $v) {
212 $this->validerAttribut($phraseur, $k, $v, $name);
216 // http://code.spip.net/@finElement
217 public function finElement($phraseur, $name) {
218 $depth = $this->depth
;
219 $contenu = $this->contenu
;
221 $n = strlen($this->res
);
222 $c = strlen(trim($contenu[$depth]));
223 $k = $this->debuts
[$depth];
225 $regle = isset($this->dtc
->regles
[$name]) ?
$this->dtc
->regles
[$name] : false;
226 $vide = ($regle == 'EMPTY');
227 // controler que les balises devant etre vides le sont
229 if ($n <> ($k +
$c)) {
230 coordonnees_erreur($this, " <p><b>$name</b> " . _T('zxml_nonvide_balise'));
232 // pour les regles PCDATA ou iteration de disjonction, tout est fait
233 } elseif ($regle and ($regle != '*')) {
235 // iteration de disjonction non vide: 1 balise au -
237 coordonnees_erreur($this, "<p>\n<b>$name</b> "
238 . _T('zxml_vide_balise'));
241 $f = isset($this->fratrie
[substr($depth, 2)]) ?
$this->fratrie
[substr($depth, 2)] : null;
242 if (is_null($f) or !preg_match($regle, $f)) {
243 coordonnees_erreur($this,
244 " <p>\n<b>$name</b> "
245 . _T('zxml_succession_fils_incorrecte')
253 if ($f = $this->process
['fin']) {
254 $f($this, $name, $vide);
258 // http://code.spip.net/@textElement
259 public function textElement($phraseur, $data) {
262 $d = $this->ouvrant
[$d];
263 preg_match('/^\s*(\S+)/', $d, $m);
264 if (isset($this->dtc
->pcdata
[$m[1]]) and ($this->dtc
->pcdata
[$m[1]])) {
265 coordonnees_erreur($this, " <p><b>" . $m[1] . "</b> "
266 . _T('zxml_nonvide_balise') // message a affiner
270 if ($f = $this->process
['text']) {
275 public function piElement($phraseur, $target, $data) {
276 if ($f = $this->process
['pi']) {
277 $f($this, $target, $data);
281 // Denonciation des entitees XML inconnues
282 // Pour contourner le bug de conception de SAX qui ne signale pas si elles
283 // sont dans un attribut, les entites les plus frequentes ont ete
284 // transcodees au prealable (sauf & < > " que SAX traite correctement).
285 // On ne les verra donc pas passer a cette etape, contrairement a ce que
286 // le source de la page laisse legitimement supposer.
288 // http://code.spip.net/@defautElement
289 public function defaultElement($phraseur, $data) {
290 if (!preg_match('/^<!--/', $data)
291 and (preg_match_all('/&([^;]*)?/', $data, $r, PREG_SET_ORDER
))
295 if (!isset($this->dtc
->entites
[$e])) {
296 coordonnees_erreur($this, " <b>$e</b> "
297 . _T('zxml_inconnu_entite')
303 if (isset($this->process
['default']) and ($f = $this->process
['default'])) {
308 // http://code.spip.net/@phraserTout
309 public function phraserTout($phraseur, $data) {
310 xml_parsestring($this, $data);
312 if (!$this->dtc
or preg_match(',^' . _MESSAGE_DOCTYPE
. ',', $data)) {
313 $this->err
[] = array('DOCTYPE ?', 0, 0);
315 $this->valider_passe2($this);
322 * @param array $process ?
324 public function __construct($process = array()) {
325 if (is_array($process)) {
326 $this->process
= $process;
330 public $ids = array();
331 public $idrefs = array();
332 public $idrefss = array();
333 public $debuts = array();
334 public $fratrie = array();
342 public $err = array();
343 public $contenu = array();
344 public $ouvrant = array();
345 public $reperes = array();
346 public $process = array(
347 'debut' => 'xml_debutElement',
348 'fin' => 'xml_finElement',
349 'text' => 'xml_textElement',
350 'pi' => 'xml_piElement',
351 'default' => 'xml_defaultElement'
357 * Retourne une structure ValidateurXML, dont le champ "err" est un tableau
358 * ayant comme entrees des sous-tableaux [message, ligne, colonne]
361 function xml_valider_dist($page, $apply = false, $process = false, $doctype = '', $charset = null) {
362 $f = new ValidateurXML($process);
363 $sax = charger_fonction('sax', 'xml');
365 return $sax($page, $apply, $f, $doctype, $charset);